<!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" th:href="@{/webjars/bootstrap/4.2.1/css/bootstrap.min.css}"/>
    <script type="text/javascript" th:src="@{/webjars/axios/0.21.1/dist/axios.min.js}"></script>
    <link href="/css/upload.css" rel="stylesheet">
</head>

<body>
<main role="main" class="container pb-3">
    <h1 class="display-3 text-center">JDK 11</h1>
    <h2 class="text-center">Safepoints and Garbage Collector log file analyzer</h2>
    <p class="alert alert-warning">
        Whole page is under heavy development, it is tested mostly on G1/Parallel GC on JDK 11. If you have any issues
        with your log file send me
        message to <a href="mailto:ks@gclogs.com">ks@gclogs.com</a>.
    </p>

    <div class="card">
        <div class="card-body">
            <h5 class="card-title">Current limitations:</h5>
            <ul>
                <li>Logs from
                    <mark>JDK 11, 12, 13</mark>
                    -
                    <mark>works with flags below</mark>
                    , tested on Parallel, CMS and G1.
                </li>
                <li>Logs from JDK 9, 10 - should work.</li>
                <li>Logs from
                    <mark>JDK 8</mark>
                    and below - experimental (
                    <mark>no GC log support</mark>
                    ,
                    only safepoint, charts and stas are not accurate, because log file style sucks).
                </li>
                <li>
                    <mark>Max <span th:text="${maxFileSize.toMegabytes()}"/>MB file (after compression).</mark>
                </li>
                <li><span>For JDK 11+ decorators: level,tags,time,uptime are needed, check sample Xlog configuration below.</span>
                </li>
                <li>You can add many log files in one zip/7z archive. The files will be ordered by first line timestamp.</li>
                <li>There is no exception handling at all :)</li>
            </ul>
            <div class="text-center">
                <button class="btn btn-primary btn-lg collapse show" id="show-upload">
                    I have read at least highlighted text from that site
                </button>
            </div>
        </div>
    </div>


    <div class="card mt-3 shadow rounded collapse" id="real-import">
        <h5 class="card-header">
            Upload file:
        </h5>
        <div class="card-body">
            <form id="fileUploadForm">
                <div class="form-group">
                    <label for="inputFile">File to upload</label>
                    <input type="file" name="file" id="inputFile" class="form-control-file"
                           placeholder="Choose file with logs"/>
                    <small class="form-text text-muted">HINT: Compress logs
                        <span class="font-weight-bold">(*.zip, *.gz, *.xz, *.7z)</span> for fast uploads.
                    </small>
                    <div class="progress" style="display: none;">
                        <div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar"
                             aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%"></div>
                    </div>
                </div>
                <input type="submit" value="Submit file" class="btn btn-primary"/>
            </form>
        </div>
        <h5 class="card-header">
            Upload logs from clipboard:
        </h5>
        <div class="card-body">
            <form id="textUploadForm">
                <div class="mb-3">
                    <textarea name="text" class="form-control" placeholder="Copy gc logs here" rows="5"></textarea>
                    <div class="progress" style="display: none;">
                        <div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar"
                             aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%"></div>
                    </div>
                </div>
                <input type="submit" value="Submit logs" class="btn btn-primary"/>
            </form>
        </div>
        <h5 class="card-header">
            Upload logs with curl:
        </h5>
        <div class="card-body">
            <div class="mb-3">
                <p>
                    Compress your logs:<br/>
                    <kbd>zip -r logs.zip logs_file.log*</kbd><br/>
                    <small class="text-muted">Make sure your logs are not bigger than <span
                            th:text="${maxFileSize.toMegabytes()}"/>MB after compression.</small>
                </p>
                <p>
                    Upload logs archive to the server:<br/>
                    <kbd>curl -X POST --form file=@./logs.zip [[${enqueueUrl}]]</kbd><br/>
                    <small class="text-muted">In response you'll receive link to your report. The report will be available for [[${parsingProperties.results.expiration.toMinutes()}]] minutes.</small>
                </p>
                <h6>Kubernetes</h6>
                <p>
                    <kbd>kubectl exec -t &lt;pod where logs are located&gt; -- curl -X POST --form
                        file=@&lt;path to your logs on container&gt; [[${enqueueUrl}]]</kbd><br/>
                    <small class="text-muted">Remember that <kbd>curl</kbd> command must be available in the
                        container</small>
                </p>
            </div>
        </div>
    </div>

    <div class="card mt-3">
        <div class="card-body">
            <h5 class="card-title">Recommended JVM arguments</h5>
            <div class="card">
                <div class="card-header">
                    <h2 class="mb-0">
                        <button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapse1"
                                aria-expanded="false" aria-controls="collapse1">
                            VM Args that I use
                        </button>
                    </h2>
                </div>
                <div id="collapse1" class="collapse">
                    <div class="card-body">
                        <pre><code>-Xlog:codecache=info,
codecache+sweep*=trace,
class+unload,
class+load,
os+thread,
safepoint,
gc*,
gc+stringdedup=debug,
gc+ergo=trace,
gc+age=trace,
gc+phases=trace,
gc+humongous=trace,
jit+compilation=debug
:file=/tmp/app.log
:level,tags,time,uptime
:filesize=104857600,filecount=5</code></pre>
                    </div>
                </div>
            </div>
            <div class="card">
                <div class="card-header">
                    <h2 class="mb-0">
                        <button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapse2"
                                aria-expanded="false" aria-controls="collapse1">
                            Simplest VM Args to add JDK > 8
                        </button>
                    </h2>
                </div>
                <div id="collapse2" class="collapse">
                    <div class="card-body">
                        <pre><code>-Xlog:safepoint,
gc*,
gc+heap=trace,
gc+age=trace,
gc+phases=trace,
gc+humongous=trace
:file=/tmp/app.log
:level,tags,time,uptime</code></pre>
                    </div>
                </div>
            </div>
            <div class="card">
                <div class="card-header">
                    <h2 class="mb-0">
                        <button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapse3"
                                aria-expanded="false" aria-controls="collapse1">
                            Simplest VM Args to add JDK <= 8
                        </button>
                    </h2>
                </div>
                <div id="collapse3" class="collapse">
                    <div class="card-body">
                        <pre><code>-XX:+PrintSafepointStatistics
-XX:PrintSafepointStatisticsCount=1
-XX:LogFile=/tmp/app.log
-XX:+LogVMOutput</code></pre>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <div class="card mt-3">
        <div class="card-body">
            <h5 class="card-title">Unified logger, changing logs at runtime example:</h5>
            <pre><code>sudo -u JVM_USER jcmd `pgrep -x java` VM.log
output="file=/PATH/TO/gc.log"
output_options="filesize=104857600,filecount=5"
what="codecache=info,codecache+sweep*=trace,class+unload,class+load,os+thread,safepoint,gc*,gc+stringdedup=debug,gc+ergo=trace,gc+age=trace,gc+phases=trace,gc+humongous=trace,jit+compilation=debug"
decorators="level,tags,time,uptime,pid</code></pre>
        </div>
    </div>

    <div class="alert alert-success mt-3">
        <h4 class="alert-heading">Terms of service:</h4>
        <span>Files you are going to upload will be stored on author server, and can be used by author for his purposes.
            This page is created for author purposes, use it at your own risk.
        </span>
    </div>
</main>
<footer class="footer mt-auto py-3">
    <div class="container text-center">
        <span class="text-muted">
        This tool is provided to you by <a href="mailto:ks@gclogs.com" class="text-muted">Krzysztof Ślusarski</a></span>
    </div>
</footer>

<script type="text/javascript" th:src="@{/webjars/jquery/2.2.4/jquery.min.js}"></script>
<script type="text/javascript" th:src="@{/webjars/bootstrap/4.2.1/js/bootstrap.min.js}"></script>

<script type="text/javascript">
    function isEmpty(str) {
        return (!str || 0 === str.length);
    }

    function uploadFile(file) {
        let payload = new FormData()
        payload.append('file', file)
        uploadLogs(payload, '#fileUploadForm', '/enqueue');
    }

    function uploadTextLogs(logsText) {
        let payload = new FormData()
        payload.append('text', logsText)
        uploadLogs(payload, '#textUploadForm', '/enqueue-plain-text');
    }

    function uploadLogs(payload, parentFormSelector, endpoint) {
        const parentElement = $(parentFormSelector);

        function toggleProgressBarVisible(visible) {
            const element = parentElement.find('.progress');
            if (visible) {
                element.show();
            } else {
                element.hide();
            }
        }

        function setProgressBarPercent(percent) {
            parentElement.find('.progress-bar').css('width', percent + '%').attr('aria-valuenow', percent);
        }

        function setButtonEnabled(enabled) {
            parentElement.find('input[type=submit]').attr("disabled", !enabled)
        }

        const config = {
            onUploadProgress: function (progressEvent) {
                const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
                setProgressBarPercent(percentCompleted);
            }
        }

        setProgressBarPercent(0);
        toggleProgressBarVisible(true);
        setButtonEnabled(false);

        axios.post(endpoint, payload, config)
            .then(res => {
                const parsingId = res.data.parsingId;
                window.open(`/parsings/${parsingId}/progress`);
                toggleProgressBarVisible(false);
                setProgressBarPercent(0);
                setButtonEnabled(true);
            })
            .catch(err => {
                toggleProgressBarVisible(false);
                setProgressBarPercent(0);
                setButtonEnabled(true);
                console.log(err);
            });
    }

    $(document).ready(function () {
        $('#show-upload').click(function () {
            $(this).collapse("hide");
            $("#real-import").collapse("show");
        });

        $("#fileUploadForm").on("submit", function () {
            const inputField = $(this).find("input");
            const selectedFiles = inputField.prop('files');
            if (selectedFiles.length) {
                const selectedFile = selectedFiles[0];
                if (selectedFile.size <= [[${maxFileSize.toBytes()}]]) {
                    uploadFile(selectedFile)
                    return false;
                } else {
                    window.alert("File is too big! The size " + (selectedFile.size / (1024 * 1024)) + "MB is more than the limit of " + [[${maxFileSize.toMegabytes()}]] + "MB");
                    return false;
                }
            } else {
                window.alert("You must first select file with logs");
                return false;
            }
        });

        $("#textUploadForm").on("submit", function () {
            var logs = $(this).find("textarea").val()
            if (isEmpty(logs)) {
                window.alert("The field with logs is empty!");
                return false;
            } else {
                uploadTextLogs(logs);
                return false;
            }
        });
    });

</script>
</body>
</html>